#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include "scheduler.h"
#include "cpu.h"

Queue getReadyQueue(const int max_num_processes);
Process getLotteryProcess(Scheduler s);

Scheduler newScheduler(const int max_processes, enum SchedulerType sched_type, CPU_SCHED cpu) {
	Scheduler sched;
	sched = (Scheduler) malloc(sizeof(SchedulerStr));
	sched->cpu = cpu;
	sched->scheduler_type = sched_type;
	sched->round_robin_queue = getReadyQueue(max_processes);
	sched->priority_queue[HIGH] = getReadyQueue(max_processes);
	sched->priority_queue[MEDIUM] = getReadyQueue(max_processes);
	sched->priority_queue[LOW] = getReadyQueue(max_processes);
	return sched;
}

Queue getReadyQueue(const int max_num_processes) {
	Queue ready_queue = createQueue(max_num_processes);
	return ready_queue;
}

void initializeScheduler(Scheduler s) {
	switch (s->scheduler_type) {
	case ROUND_ROBIN:
		s->running = frontAndDequeue(s->round_robin_queue);
		break;
	case PRIORITY:
		if (!isEmpty(s->priority_queue[HIGH])) {
			s->running = frontAndDequeue(s->priority_queue[HIGH]);
		} else if (!isEmpty(s->priority_queue[MEDIUM])) {
			s->running = frontAndDequeue(s->priority_queue[MEDIUM]);
		} else {
			s->running = frontAndDequeue(s->priority_queue[LOW]);
		}
		break;
	case LOTTERY:
		s->running = getLotteryProcess(s);
		break;
	}

	return;
}

Process getLotteryProcess(Scheduler s) {
	time_t dummy;
	Process p;

	srand((unsigned int) time(&dummy));
	do {
	p = ((CPU) s->cpu)->process[rand() % ((CPU) s->cpu)->num_processes];
	} while (p->pcb->state != READY);
	return p;
}

Process getRunningProcess(Scheduler s) {
	return s->running;
}

Process switchProcess(Scheduler s, int pc) {
	Process next;
	switch (s->running->pcb->state) {
	case INTERRUPTED:
		s->running->pcb->state = READY;
		s->running->pcb->next_step = pc;
		next = getFromReadyQueue(s);
		addToReadyQueue(s, s->running);
		if (next != NULL) {
			s->running = next;
			s->running->pcb->state = RUNNING;
		}
		break;
	case BLOCKED:
		s->running->pcb->next_step = pc;
		next = getFromReadyQueue(s);
		if (next != NULL) {
			s->running = next;
			s->running->pcb->state = RUNNING;
		}
		break;
	case READY:
		s->running->pcb->next_step = pc;
		next = getFromReadyQueue(s);
		addToReadyQueue(s, s->running);

		if (next != NULL) {
			s->running = next;
			s->running->pcb->state = RUNNING;
		}
		break;
	case RUNNING:
		s->running->pcb->state = READY;
		s->running->pcb->next_step = pc;

		next = getFromReadyQueue(s);
		addToReadyQueue(s, s->running);
		if (next != NULL) {
			s->running = next;
			s->running->pcb->state = RUNNING;
		}
		break;
	}
	return s->running;
}

void addToReadyQueue(Scheduler s, Process p) {
	switch (s->scheduler_type) {
	case ROUND_ROBIN:
		enqueue(p, s->round_robin_queue);
		break;
	case PRIORITY:
		switch (p->process_type) {
		case UI:
			enqueue(p, s->priority_queue[HIGH]);
			break;
		case PRODUCER:
			enqueue(p, s->priority_queue[MEDIUM]);
			break;
		case CONSUMER:
			enqueue(p, s->priority_queue[MEDIUM]);
			break;
		case CALCULATOR:
			enqueue(p, s->priority_queue[LOW]);
			break;
		}
		break;
	case LOTTERY:
		break;
	}
	return;
}

Process getFromReadyQueue(Scheduler s) {
	Process p;
		switch (s->scheduler_type) {
		case ROUND_ROBIN:
			p = frontAndDequeue(s->round_robin_queue);
			break;
		case PRIORITY:
			if (!isEmpty(s->priority_queue[HIGH])) {
				p = frontAndDequeue(s->priority_queue[HIGH]);
			} else if (!isEmpty(s->priority_queue[MEDIUM])) {
				p = frontAndDequeue(s->priority_queue[MEDIUM]);
			} else {
				p = frontAndDequeue(s->priority_queue[LOW]);
			}
			break;
		case LOTTERY:
			p = getLotteryProcess(s);
			break;
		}
		return p;
}

Process getNextProcess(Scheduler s) {
	Process p;
	switch (s->scheduler_type) {
	case ROUND_ROBIN:
		p = front(s->round_robin_queue);
		break;
	case PRIORITY:
		if (!isEmpty(s->priority_queue[HIGH])) {
			p = front(s->priority_queue[HIGH]);
		} else if (!isEmpty(s->priority_queue[MEDIUM])) {
			p = front(s->priority_queue[MEDIUM]);
		} else {
			p = front(s->priority_queue[LOW]);
		}
		break;
	case LOTTERY:
		p = NULL;
		break;
	}
	return p;
}

// TODO implement different scheduling types

